package com.zb.store.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.zb.code.service.ConCodeService;
import com.zb.constant.CodeConstant;
import com.zb.constant.StoreConstant;
import com.zb.entity.*;
import com.zb.extend.StoreOutboundExtend;
import com.zb.extend.StoreOutboundMaterialExtend;
import com.zb.result.PageUtil;
import com.zb.service.*;
import com.zb.service.impl.StoreBoundServiceLog;
import com.zb.store.service.ConStoreOutboundService;
import com.zb.utils.ZbOrderNoUtil;
import com.zb.view.ViewGoods;
import com.zb.view.ViewStoreStoreGoodsStock;
import com.zb.vo.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author WEN
 * @Date 2024/1/8 0008
 **/
@Service
public class ConStoreOutboundServiceImpl implements ConStoreOutboundService {
    @Resource
    private ZjStoreSetService storeSetService;
    @Resource
    private ZjCodeService codeService;
    @Resource
    private ViewGoodsService viewGoodsService;
    @Resource
    private ZjStoreService storeService;
    @Resource
    private ZjStorePositionService storePositionService;
    @Resource
    private ZjStoreOutboundService storeOutboundService;
    @Resource
    private ZjStoreOutboundReceiptSendService storeOutboundReceiptSendService;
    @Resource
    private ZjUserService userService;
    @Resource
    private ZjStoreGoodsStockCodeService storeGoodsStockCodeService;
    @Resource
    private ZjStoreOutboundMaterialService storeOutboundMaterialService;
    @Resource
    private ZjStoreGoodsStockService storeGoodsStockService;
    @Resource
    private ZjStoreTypeService storeTypeService;
    @Resource
    private ZjStoreLogisticsService storeLogisticsService;
    @Resource
    private ZjStoreOutboundMaterialCodeService storeOutboundMaterialCodeService;
    @Resource
    private ZjStoreOutboundOrderSpareService storeOutboundOrderSpareService;
    @Resource
    private ConCodeService conCodeService;
    @Resource
    private ViewStoreStoreGoodsStockService viewStoreStoreGoodsStockService;

    // 保存或审核出库单
    @Override
    @Transactional
    public void saveOrCheck(AccounLoginInfo loginInfo, String submitType, ZjStoreOutboundOrderSpare outboundOrderSpare, StoreOutboundVo outboundVo) {
        String outboundNo = "";
        if (null != outboundVo.getId()) {
            ZjStoreOutbound outbound = storeOutboundService.getById(outboundVo.getId());
            Assert.notNull(outbound, "出库单[ "+outboundVo.getId()+" ]的数据不存在");
            Assert.isTrue(Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_1),
                    Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_2) ? "该出库单已审核，禁止重复操作" :
                            Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_3) ? "出库单已被审核并驳回，禁止重复操作" :
                                    Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_4) ? "出库单已确认出库，禁止重复操作" : "出库单已作废，禁止重复操作");
            outboundNo = outbound.getCode();
        }
        // 校验出库类型
        ZjStoreType storeType = storeTypeService.getById(outboundVo.getTypeId());
        Assert.notNull(storeType, "出库类型参数[ "+outboundVo.getTypeId()+" ]的数据不存在");

        StoreOutboundReceiptVo receiptVo = outboundVo.getReceiptVo();
        if (receiptVo.getReceiptAddrType() != StoreConstant.STORE_OUT_RECEIPTS_TYPE_4) {
            if (receiptVo.getReceiptAddrType() == StoreConstant.STORE_OUT_RECEIPTS_TYPE_1) {
                ZjUser user = userService.getById(receiptVo.getRelationId());
                Assert.notNull(user, "收货客户的数据不存在");
            } else {
                ZjStore store = storeService.getById(receiptVo.getRelationId());
                Assert.notNull(store, "收货仓库[ " + (Objects.equals(2, receiptVo.getReceiptAddrType()) ? "服务商仓库" : "工程师仓库") + " ]的数据不存在");
                if (store.getEnableStorePosition()) {
                    Assert.isTrue(null != receiptVo.getStorePositionId() && receiptVo.getStorePositionId() > 0, "仓库已启用仓位，请指定收货仓位");
                    ZjStorePosition storePosition = storePositionService.getById(receiptVo.getStorePositionId());
                    Assert.notNull(storePosition, "仓位数据不存在！请检查仓位数据是否正确！");
                }
            }
        }
        List<StoreBoundMaterialVo> materialVos = outboundVo.getOutBoundMaterialVos();
        List<Long> goodsIds = materialVos.stream().map(StoreBoundMaterialVo::getGoodsId).distinct().toList();;
        List<Long> goodsSpecIds = materialVos.stream().map(StoreBoundMaterialVo::getGoodsSpecId).distinct().toList();
        List<ViewGoods> viewGoodsList = viewGoodsService.getByGoodsIdsAndGoodsSpecIds(goodsIds, goodsSpecIds);
        // 根据商品类型&商品ID&商品规格ID存储商品对象
        Map<String, ViewGoods> viewGoodsMap = new HashMap<>();

        List<Long> storeIds = new ArrayList<>();
        ViewGoods viewGoods = null;
        // 校验出库商品物料
        for (StoreBoundMaterialVo vo : materialVos) {
            vo.setStorePositionId(null == vo.getStorePositionId()? 0L : vo.getStorePositionId());
            viewGoods = viewGoodsList.stream().filter(f -> Objects.equals(f.getGoodsType(), vo.getType()) && Objects.equals(f.getId(), vo.getGoodsId())
                    && Objects.equals(f.getSpecId(), vo.getGoodsSpecId())).findFirst().orElse(null);
            // 判断出库物料是否存在系统
            Assert.notNull(viewGoods, "出库商品型号规格参数[ " + vo.getGoodsId() + " " + vo.getGoodsSpecId() + " ]的物料数据在系统中不存在");
            // 校验仓库是否为空
            Assert.isTrue(null != vo.getStoreId() && vo.getStoreId() > 0, "出库商品型号规格[ "+ viewGoods.getModel() + " " + viewGoods.getSpecName() +" ]的出库仓库不能为空");
            storeIds.add(vo.getStoreId());
            viewGoodsMap.putIfAbsent(vo.getType() +"_" + vo.getGoodsId() + "_" + vo.getGoodsSpecId(), viewGoods);
        }
        // 仓库与仓库商品库存
        List<ViewStoreStoreGoodsStock> viewStoreStoreGoodsStockList = viewStoreStoreGoodsStockService.getByStoreIds(storeIds);
        // 存储出库物料编码
        List<String> codes = new ArrayList<>();
        // 校验仓库
        checkStore(materialVos, viewGoodsMap, viewStoreStoreGoodsStockList, codes);

        List<ZjCode> codeList = new ArrayList<>();
        List<ZjStoreGoodsStockCode> storeGoodsStockCodes = new ArrayList<>();
        ZjStoreGoodsStockCode storeGoodsStockCode = null;
        // 校验一物一码，非工单配件出库时执行
        if (null == outboundOrderSpare) {
            checkCode(loginInfo, outboundVo.getId(), codeList, codes, storeGoodsStockCodes, viewGoodsMap, materialVos);
        }
        // 更新时，查询判断提交的一物一码与存在的一物一码做对比，
        // 1.提交与存在的一物一码相同，则不理会
        // 2.提交与存在的一物一码不相同，则删除存在一物一码，保存提交的一物一码
        List<Long> delCodeIds = new ArrayList<>();
        // 存储已存在出库物料的一物一码
        Map<String, Boolean> existCodeMap = new HashMap<>();
        if (null != outboundVo.getId()) {
            // 更新时间，查询判断提交的与系统存在的出库物料做对比
            List<ZjStoreOutboundMaterial> outboundMaterialList = storeOutboundMaterialService.getByOutboundId(outboundVo.getId());
            List<ZjStoreOutboundMaterialCode> materialCodes = storeOutboundMaterialCodeService.getbyStoreOutboundId(outboundVo.getId());
            Map<String, Long> codeMap = materialCodes.stream().collect(Collectors.toMap(ZjStoreOutboundMaterialCode::getCode, ZjStoreOutboundMaterialCode::getCodeId));
            ZjStoreOutboundMaterial outboundMaterial = null;
            for (StoreBoundMaterialVo vo : materialVos) {
                // 判断删除的与更新的物料是否存在相同，相同则把删除的物料ID移除
                if (outboundVo.getDelOutBoundMaterialIds().stream().anyMatch(f -> Objects.equals(f, vo.getId()))) {
                    outboundVo.getDelOutBoundMaterialIds().remove(vo.getId());
                }

                outboundMaterial = outboundMaterialList.stream().filter(f -> Objects.equals(f.getType(), vo.getType()) && Objects.equals(f.getStoreId(), vo.getStoreId())
                        && Objects.equals(f.getStorePositionId(), vo.getStorePositionId()) && Objects.equals(f.getGoodsId(), vo.getGoodsId())
                                && Objects.equals(f.getGoodsSpecId(), vo.getGoodsSpecId())).findFirst().orElse(null);
                if (null != outboundMaterial) {
                    vo.setId(outboundMaterial.getId());
                    outboundMaterialList.remove(outboundMaterial);
                }
                for (StoreGoodsStockCodeVo codeVo : vo.getStockCodeVos()) {
                    if (null != codeMap.get(codeVo.getCode())) {
                        existCodeMap.put(codeVo.getCode(), true);
                        codeMap.remove(codeVo.getCode()); // 存在的则移除，剩下的表示删除的
                    }
                }
            }
            // 获取删除一物一码的ID
            if (CollectionUtil.isNotEmpty(outboundVo.getDelOutBoundMaterialIds())) {
                for (Long materialId : outboundVo.getDelOutBoundMaterialIds()) {
                    if (materialCodes.stream().anyMatch(f -> Objects.equals(f.getStoreOutboundMaterialId(), materialId))) {
                        delCodeIds.addAll(materialCodes.stream().filter(f -> Objects.equals(f.getStoreOutboundMaterialId(), materialId)).map(ZjStoreOutboundMaterialCode::getCodeId).toList());
                    }
                }
            }
            // 获取删除的一物一码
            for (Map.Entry<String, Long> entry : codeMap.entrySet()) {
                if (!delCodeIds.contains(entry.getValue())) {
                    delCodeIds.add(entry.getValue());
                }
            }
        }
        ZjStoreOutbound outbound = new ZjStoreOutbound();
        BeanUtil.copyProperties(outboundVo, outbound);
        if (null != outboundVo.getCreateTime()) outbound.setGmtCreate(outboundVo.getCreateTime());
        outbound.setUpdateEmpId(loginInfo.getId());
        outbound.setUpdateEmp(loginInfo.getName());
        outbound.setStatus(StoreConstant.STORE_OUT_STATUS_1);
        if (null == outbound.getId()) {
            String outStorePrefix = storeSetService.getOutStorePrefix(loginInfo.getCustAccId());
            outbound.setId(IdWorker.getId());
            outbound.setCustAccId(loginInfo.getCustAccId());
            outbound.setCode(outStorePrefix + ZbOrderNoUtil.outStoreNo(loginInfo.getCustAccId()));
            outbound.setRemark(null == outbound.getRemark()? "" : outbound.getRemark());
            outbound.setReviewEmpId(0L);
            outbound.setReviewEmp("");
            outbound.setReviewRemark("");
            outbound.setRejectEmpId(0L);
            outbound.setRejectEmp("");
            outbound.setRejectRemark("");
            outbound.setSendGoodsEmpId(0L);
            outbound.setSendGoodsEmp("");
            outbound.setSendGoodsRemark("");
            outbound.setCancelEmpId(0L);
            outbound.setCancelEmp("");
            outbound.setCancelRemark("");
            outbound.setCloseEmpId(0L);
            outbound.setCloseEmp("");
            outbound.setCloseRemark("");
            outbound.setBackInventory(false);
            outbound.setCreateEmpId(loginInfo.getId());
            outbound.setCreateEmp(loginInfo.getName());
        } else {
            outbound.setCode(outboundNo);
        }
        if (Objects.equals("2", submitType)) {
            outbound.setStatus(StoreConstant.STORE_OUT_STATUS_2);
            outbound.setRemark(null == outbound.getRemark()? "" : outbound.getRemark());
            outbound.setReviewEmpId(loginInfo.getId());
            outbound.setReviewEmp(loginInfo.getName());
            outbound.setReviewTime(new Date());
            outbound.setReviewRemark(StringUtils.isNotBlank(outboundVo.getReviewRemark())? outbound.getReviewRemark() : "");
        }
        // 创建工单出库配件的一物一码
        if (null != outboundOrderSpare) {
            getOrderSpareOutStoreCode(loginInfo, outbound.getCode(), materialVos, viewGoodsList, codeList);
        }
        List<ZjStoreOutboundMaterial> addStoreOutboundMaterialList = new ArrayList<>();
        List<ZjStoreOutboundMaterial> updateStoreOutboundMaterialList = new ArrayList<>();
        ZjStoreOutboundMaterial outboundMaterial = null;
        List<ZjStoreOutboundMaterialCode> materialCodeList = new ArrayList<>();
        ZjStoreOutboundMaterialCode materialCode = null;
        // 仓库商品库存
        List<ZjStoreGoodsStock> storeGoodsStockList = new ArrayList<>();
        ZjStoreGoodsStock storeGoodsStock = null;
        List<ZjStoreGoodsStockCode> storeGoodsStockCodeList = new ArrayList<>();
        // 仓库商品库存一物一码ID
        Long storeGoodsStockCodeId = null;
        ViewStoreStoreGoodsStock viewStoreStoreGoodsStock = null;
        for (StoreBoundMaterialVo vo : materialVos) {
            outboundMaterial = new ZjStoreOutboundMaterial();
            BeanUtil.copyProperties(vo, outboundMaterial);
            outboundMaterial.setStatus(Integer.parseInt(submitType));
            if (null != outboundMaterial.getId()) {
                updateStoreOutboundMaterialList.add(outboundMaterial);
            } else {
                outboundMaterial.setId(IdWorker.getId());
                outboundMaterial.setCustAccId(loginInfo.getCustAccId());
                outboundMaterial.setStoreOutboundId(outbound.getId());
                addStoreOutboundMaterialList.add(outboundMaterial);
            }
            // 仓库商品库存
            viewStoreStoreGoodsStock = viewStoreStoreGoodsStockList.stream().filter(f -> Objects.equals(f.getId(), vo.getStoreId()) &&  Objects.equals(f.getStorePositionId(), vo.getStorePositionId()) &&
                    Objects.equals(f.getGoodsType(), vo.getType()) && Objects.equals(f.getGoodsId(), vo.getGoodsId())&& Objects.equals(f.getGoodsSpecId(), vo.getGoodsSpecId()) ).findFirst().orElse(null);
            // 仓库商品库存
            storeGoodsStock = new ZjStoreGoodsStock();
            storeGoodsStock.setId(null == viewStoreStoreGoodsStock? IdWorker.getId() : viewStoreStoreGoodsStock.getStoreGoodsStockId());
            storeGoodsStock.setCustAccId(loginInfo.getCustAccId());
            storeGoodsStock.setSystemStock(Objects.equals("1", submitType) ? 0L : new BigDecimal("0").subtract(new BigDecimal(vo.getQty() + "")).longValue());
            storeGoodsStock.setStoreId(vo.getStoreId());
            storeGoodsStock.setStorePositionId(vo.getStorePositionId());
            storeGoodsStock.setGoodsType(vo.getType());
            storeGoodsStock.setGoodsId(vo.getGoodsId());
            storeGoodsStock.setGoodsSpecId(vo.getGoodsSpecId());
            storeGoodsStock.setPrice(vo.getPrice());
            storeGoodsStockList.add(storeGoodsStock);

            // 出库物料一物一码
            if (CollectionUtil.isNotEmpty(vo.getStockCodeVos())) {
                for (StoreGoodsStockCodeVo code : vo.getStockCodeVos()) {
                    Long codeId = codeList.stream().filter(f -> Objects.equals(f.getSecurityCode(), code.getCode()) && Objects.equals(f.getGoodsId(), vo.getGoodsId())
                            && Objects.equals(f.getType(), vo.getType()) && Objects.equals(f.getGoodsSpecId(), vo.getGoodsSpecId())).map(ZjCode::getId).findFirst().get();
                    // 非工单配件出库则不执行
                    if (null == outboundOrderSpare) {
                        // 一物一码更新信息
                        codeList.stream().filter(f -> Objects.equals(f.getId(), codeId)).forEach(t -> {
                            t.setStoreId(vo.getStoreId());
                            t.setStorePositionId(vo.getStorePositionId());
                            t.setStoreStatus(Objects.equals("2", submitType) ? CodeConstant.STORE_STATUS_5 : CodeConstant.STORE_STATUS_4);
                        });
                    }
                    // 不存在则新增
                    if (null == existCodeMap.get(code.getCode())) {
                        materialCode = new ZjStoreOutboundMaterialCode();
                        materialCode.setId(IdWorker.getId());
                        materialCode.setCustAccId(loginInfo.getCustAccId());
                        materialCode.setStoreOutboundId(outbound.getId());
                        materialCode.setStoreOutboundMaterialId(outboundMaterial.getId());
                        materialCode.setType(vo.getType());
                        materialCode.setCodeId(codeId);
                        materialCode.setCode(code.getCode());
                        materialCodeList.add(materialCode);
                    }
                    // 仓库商品库存一物一码
                    storeGoodsStockCodeId = storeGoodsStockCodes.stream().filter(f -> Objects.equals(f.getCode(), code.getCode())).findFirst().map(ZjStoreGoodsStockCode::getId).orElse(null);
                    storeGoodsStockCode = new ZjStoreGoodsStockCode();
                    storeGoodsStockCode.setOutStoreType(Integer.parseInt(submitType));
                    storeGoodsStockCode.setStoreOutboundId(outbound.getId());
                    storeGoodsStockCode.setStoreOutboundCode(outbound.getCode());
                    storeGoodsStockCode.setStoreOutboundMaterialId(outboundMaterial.getId());
                    if (null == storeGoodsStockCodeId) {
                        storeGoodsStockCode.setCustAccId(loginInfo.getCustAccId());
                        storeGoodsStockCode.setStoreId(vo.getStoreId());
                        storeGoodsStockCode.setStorePositionId(vo.getStorePositionId());
                        storeGoodsStockCode.setStoreGoodsStockId(storeGoodsStock.getId());
                        storeGoodsStockCode.setCodeId(codeId);
                        storeGoodsStockCode.setCode(code.getCode());
                        storeGoodsStockCode.setInStoreType(0);
                        storeGoodsStockCode.setStoreInboundId(0L);
                        storeGoodsStockCode.setStoreInboundCode("");
                        storeGoodsStockCode.setStoreInboundMaterialId(0L);
                    } else {
                        storeGoodsStockCode.setId(storeGoodsStockCodeId);
                    }
                    storeGoodsStockCodeList.add(storeGoodsStockCode);
                }
            }
        }
        // 根据删除的一物一码ID，获取商品库存一物一码信息
        List<Long> delStoreGoodsStockCodeIds = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(delCodeIds)) {
            List<ZjStoreGoodsStockCode> stockCodeList = storeGoodsStockCodeService.getByCodeIds(loginInfo.getCustAccId(), delCodeIds);
            delStoreGoodsStockCodeIds.addAll(stockCodeList.stream().map(ZjStoreGoodsStockCode::getId).toList());
        }
        storeOutboundService.saveOrUpdate(outbound);

        // 删除入库物料 & 入库物料一物一码
        if (CollectionUtil.isNotEmpty(outboundVo.getDelOutBoundMaterialIds())){
            storeOutboundMaterialService.removeByIds(outboundVo.getDelOutBoundMaterialIds());
        }
        storeOutboundMaterialCodeService.deletebyCodeIds(delCodeIds);
        // 删除仓库商品库存一物一码
        storeGoodsStockCodeService.deleteByIds(delStoreGoodsStockCodeIds);

        storeOutboundReceiptSendService.save(loginInfo.getCustAccId(), outbound.getId(), outboundVo.getReceiptVo());
        if (CollectionUtil.isNotEmpty(addStoreOutboundMaterialList)) storeOutboundMaterialService.saveBatch(addStoreOutboundMaterialList);
        if (CollectionUtil.isNotEmpty(updateStoreOutboundMaterialList)) storeOutboundMaterialService.updateBatchById(updateStoreOutboundMaterialList);
        if (CollectionUtil.isNotEmpty(materialCodeList)) storeOutboundMaterialCodeService.saveBatch(materialCodeList);
        // 仓库商品库存
        storeGoodsStockService.save(storeGoodsStockList, storeGoodsStockCodeList);
        if (null != outboundOrderSpare) {
            outboundOrderSpare.setStoreOutboundId(outbound.getId());
            storeOutboundOrderSpareService.save(outboundOrderSpare);
            StoreBoundServiceLog.saveOutboundLog(new StoreBoundVo(loginInfo, outbound.getId(), outbound.getCode(), "工单配件保存并审核出库", outbound.getRemark()));
        } else {
            // 更改一物一码前，移除同时存在的一物一码ID
            delCodeIds.removeIf(f -> codeList.stream().map(ZjCode::getId).anyMatch(f2 -> Objects.equals(f2, f)));
            codeService.updateInbound(loginInfo, true, 1, delCodeIds);
            codeService.saveOutStoreActiveCode(loginInfo, submitType, storeSetService.getOutStoreCodeActiveMode(loginInfo.getCustAccId()), outbound.getCode(), null, codeList);
            StoreBoundServiceLog.saveOutboundLog(new StoreBoundVo(loginInfo, outbound.getId(), outbound.getCode(), Objects.equals("2", submitType) ? "保存出库单" : "保存并审核出库", outbound.getRemark()));
        }
    }

    /**
     * 校验仓库
     * @param materialVos
     * @param viewGoodsMap
     * @param viewStoreStoreGoodsStockList 仓库与仓库商品库存对象
     * @param codes
     */
    private void checkStore(List<StoreBoundMaterialVo> materialVos, Map<String, ViewGoods> viewGoodsMap, List<ViewStoreStoreGoodsStock> viewStoreStoreGoodsStockList, List<String> codes) {
        List<ZjStorePosition> storePositionList = storePositionService.getStorePositionNameByIds(materialVos.stream().filter(f -> null != f.getStorePositionId() && f.getStorePositionId() > 0)
                .map(StoreBoundMaterialVo::getStorePositionId).distinct().toList());
        ViewGoods viewGoods = null;
        ZjStore store = null;
        ZjStorePosition storePosition = null;
        ViewStoreStoreGoodsStock viewStoreStoreGoodsStock = null;
        for (StoreBoundMaterialVo vo : materialVos) {
            viewGoods = viewGoodsMap.get(vo.getType() + "_" + vo.getGoodsId() + "_" +vo.getGoodsSpecId());
            // 获取仓库
            store = viewStoreStoreGoodsStockList.stream().filter(f -> Objects.equals(f.getId(), vo.getStoreId())).findFirst().orElse(null);
            Assert.notNull(store, "出库商品型号规格[ " + viewGoods.getModel() + " " + viewGoods.getSpecName() + " ]的出库仓库参数[ "+vo.getStoreId()+" ]的数据不存在系统中");
            // 获取仓库对应的商品库存
            viewStoreStoreGoodsStock = viewStoreStoreGoodsStockList.stream().filter(f -> Objects.equals(f.getId(), vo.getStoreId()) && Objects.equals(f.getGoodsType(), vo.getType())
                    && Objects.equals(f.getGoodsId(), vo.getGoodsId()) && Objects.equals(f.getGoodsSpecId(), vo.getGoodsSpecId())).findFirst().orElse(null);
            // 是否允许负库存出库
            if (!store.getCanNegativeStock() && (null == viewStoreStoreGoodsStock || viewStoreStoreGoodsStock.getSystemStock() <= 0)) {
                Assert.isTrue(false, "出库仓库[ " + store.getName() + " ]不允许负库存出库");
            }
            // 是否启用仓位
            if (store.getEnableStorePosition()) {
                storePosition = storePositionList.stream().filter(f -> Objects.equals(f.getId(), vo.getStorePositionId())).findFirst().orElse(null);
                Assert.notNull(storePosition, "出库仓位参数[ "+vo.getStorePositionId()+" ]的数据不存在系统中");
                Assert.isTrue(Objects.equals(store.getId(), storePosition.getStoreId()),
                        "商品型号规格[ " + viewGoods.getModel() + " " + viewGoods.getSpecName() + " ]的出库仓位[ "+storePosition.getName()+" ]不属于仓库[ "+store.getName()+" ]下的仓位数据，请更改后重新出库");
            }
            // 存储出库物料一物一码
            if (CollectionUtil.isNotEmpty(vo.getStockCodeVos())) codes.addAll(vo.getStockCodeVos().stream().map(StoreGoodsStockCodeVo::getCode).distinct().toList());
        }
    }

    /**
     * 校验一物一码
     * @param loginInfo
     * @param outboundId
     * @param codeList
     * @param codes
     * @param storeGoodsStockCodes
     * @param viewGoodsMap
     * @param materialVos
     */
    private void checkCode(AccounLoginInfo loginInfo, Long outboundId, List<ZjCode> codeList, List<String> codes, List<ZjStoreGoodsStockCode> storeGoodsStockCodes, Map<String, ViewGoods> viewGoodsMap, List<StoreBoundMaterialVo> materialVos) {
        codeList.addAll(codeService.checkByCodes(loginInfo.getCustAccId(), codes));
        storeGoodsStockCodes.addAll(storeGoodsStockCodeService.getByCodes(loginInfo.getCustAccId(), codes));
        ZjStoreGoodsStockCode storeGoodsStockCode = null;
        ViewGoods viewGoods = null;
        Map<String, Boolean> existCode = new HashMap<>();
        // 无效一物一码集合
        List<String> invalidCodes = new ArrayList<>();
        for (StoreBoundMaterialVo vo : materialVos) {
            viewGoods = viewGoodsMap.get(vo.getType() + "_" + vo.getGoodsId() + "_" + vo.getGoodsSpecId());
            // 校验出库物料与一物一码数量
            Assert.isTrue(vo.getQty() == vo.getStockCodeVos().stream().map(StoreGoodsStockCodeVo::getCode).filter(StringUtils::isNotBlank).toList().size(),
                    "出库商品型号规格[ " + viewGoods.getModel() + " " + viewGoods.getSpecName() + " ]数量与商品一物一码数量不一至");
            // 校验一物一码是否重复
            for (StoreGoodsStockCodeVo code : vo.getStockCodeVos()) {
                Assert.isNull(existCode.get(code.getCode()), "入库商品型号规格[ " + viewGoods.getModel() + " " + viewGoods.getSpecName() + " ]与商品一物一码[ " + code.getCode() + " ]已重复");
                existCode.put(code.getCode(), true);
            }
            invalidCodes.clear();

            // 校验一物一码是否在系统中
            invalidCodes.addAll(vo.getStockCodeVos().stream().map(StoreGoodsStockCodeVo::getCode).filter(f -> codeList.stream().noneMatch(f2 -> Objects.equals(f2.getSecurityCode(), f))).toList());
            Assert.isTrue(CollectionUtil.isEmpty(invalidCodes), "商品型号规格[ "+ viewGoods.getModel() + " " + viewGoods.getSpecName() +" ]与绑定的一物一码[ " +
                    String.join("、", invalidCodes)+ " ]的数据不存在系统中");

            invalidCodes.clear();
            // 校验物料与一物一码是否对应
            invalidCodes.addAll(vo.getStockCodeVos().stream().map(StoreGoodsStockCodeVo::getCode).filter(code -> codeList.stream().noneMatch(f2 -> Objects.equals(f2.getType(), vo.getType()) && Objects.equals(f2.getSecurityCode(), code)
                    && Objects.equals(f2.getGoodsId(), vo.getGoodsId()) && Objects.equals(f2.getGoodsSpecId(), vo.getGoodsSpecId()))).toList());
            Assert.isTrue(CollectionUtil.isEmpty(invalidCodes), "商品型号规格[ "+ viewGoods.getModel() + " " + viewGoods.getSpecName() +" ]与绑定的一物一码[ " +
                    String.join("、", invalidCodes)+ " ]不匹配，请检查型号规格与一物一码是否错误");

            // 校验一物一码是否已经入库，非待审核的
            invalidCodes.clear();
            invalidCodes.addAll(vo.getStockCodeVos().stream().map(StoreGoodsStockCodeVo::getCode).filter(f -> storeGoodsStockCodes.stream().anyMatch(f2 -> Objects.equals(f2.getCode(), f)
                    && Objects.equals(f2.getInStoreType(), StoreConstant.GOODS_CODE_IN_STORE_TYPE_1))).toList());
            Assert.isTrue(CollectionUtil.isEmpty(invalidCodes), "商品型号规格[ " + viewGoods.getModel() + " " + viewGoods.getSpecName() + " ]与绑定的一物一码[ " +
                    String.join("、", invalidCodes) + " ]正在入库待审核中，禁止一物一码出库");

            // 校验一物一码是否已经入库，入库则校验一物一码出库的仓库是否一致
            invalidCodes.clear();
            invalidCodes.addAll(vo.getStockCodeVos().stream().map(StoreGoodsStockCodeVo::getCode).filter(f -> storeGoodsStockCodes.stream().anyMatch(f2 -> Objects.equals(f2.getCode(), f)
                    && Objects.equals(f2.getInStoreType(), StoreConstant.GOODS_CODE_IN_STORE_TYPE_2) && Objects.equals(f2.getStoreId(), vo.getStoreId())
                    && Objects.equals(f2.getStorePositionId(), vo.getStorePositionId()))).toList());
            Assert.isTrue(CollectionUtil.isEmpty(invalidCodes), "商品型号规格[ " + viewGoods.getModel() + " " + viewGoods.getSpecName() + " ]与绑定的一物一码[ " +
                    String.join("、", invalidCodes) + " ]的出库仓库与一物一码入库的仓库不一致！");

            // 校验一物一码是否已经出库
            invalidCodes.clear();
            if (null == outboundId) {
                invalidCodes.addAll(vo.getStockCodeVos().stream().map(StoreGoodsStockCodeVo::getCode).filter(f -> storeGoodsStockCodes.stream().anyMatch(f2 -> Objects.equals(f2.getCode(), f)
                        && (Objects.equals(f2.getOutStoreType(), StoreConstant.GOODS_CODE_OUT_STORE_TYPE_1) || Objects.equals(f2.getOutStoreType(), StoreConstant.GOODS_CODE_OUT_STORE_TYPE_2)))).toList());
                Assert.isTrue(CollectionUtil.isEmpty(invalidCodes), "商品型号规格[ " + viewGoods.getModel() + " " + viewGoods.getSpecName() + " ]与绑定的一物一码[ " +
                        String.join("、", invalidCodes) + " ]已经出库，禁止重复出库");
            } else {
                for (StoreGoodsStockCodeVo codeVo : vo.getStockCodeVos()) {
                    storeGoodsStockCode = storeGoodsStockCodes.stream().filter(f -> Objects.equals(f.getCode(), codeVo.getCode())).findFirst().orElse(null);
                    if (null != storeGoodsStockCode && storeGoodsStockCode.getStoreOutboundId() > 0 && !Objects.equals(storeGoodsStockCode.getStoreOutboundId(), outboundId)) {
                        Assert.isTrue(false, "商品型号规格[ " + viewGoods.getModel() + " " + viewGoods.getSpecName() + " ]与绑定的一物一码[ " +
                                codeVo.getCode() + " ]已经出库，禁止重复出库");
                    }
                }
            }
            vo.getStockCodeVos().forEach(t -> {
                t.setCodeId(codeList.stream().filter(f -> Objects.equals(f.getSecurityCode(), t.getCode())).map(ZjCode::getId).findFirst().orElse(0L));
            });
        }
    }

    // 工单配件出库，创建临时编码
    private void getOrderSpareOutStoreCode(AccounLoginInfo loginInfo, String outboundNo, List<StoreBoundMaterialVo> materialVos, List<ViewGoods> viewGoodsList, List<ZjCode> codeList) {
        // 工单配件出库，创建临时编码
//        List<CodeVo> codeVos = new ArrayList<>();
//        ViewGoods viewGoods = null;
//        for (StoreBoundMaterialVo vo : materialVos) {
//            viewGoods = viewGoodsList.stream().filter(f -> Objects.equals(f.getId(), vo.getGoodsId()) && Objects.equals(f.getSpecId(), vo.getGoodsSpecId())).findFirst().get();
//            CodeVo codeVo = new CodeVo();
//            codeVo.setGoodsId(vo.getGoodsId());
//            codeVo.setGoodsSpecId(vo.getGoodsSpecId());
//            codeVo.setQty((int) vo.getQty());
//            codeVo.setWarrantyDay(viewGoods.getWarrantyDay());
//            codeVo.setStoreId(vo.getStoreId());
//            codeVo.setStoreIdPositionId(vo.getStorePositionId());
//            codeVos.add(codeVo);
//        }
//        Map<String, List<ZjCode>> codeMap = conCodeService.saveSpareTemporaryCode(loginInfo, true, outboundNo, codeVos);
//        for (StoreBoundMaterialVo vo : materialVos) {
//            codeList.addAll(codeMap.get(vo.getGoodsId() + "_" + vo.getGoodsSpecId()));
//            vo.setStockCodeVos(codeList.stream().map(code -> {
//                return new StoreGoodsStockCodeVo(null, code.getSecurityCode());
//            }).toList());
//        }
    }

    // 获取出库列表
    @Override
    public Map<String, Object> getList(Long custAccId, StoreOutboundListSelectVo selectVo) {
        PageUtil.setPageLimit(selectVo);
        List<StoreOutboundExtend> outboundExtendList = storeOutboundService.getList(custAccId, selectVo);
        if (CollectionUtil.isEmpty(outboundExtendList)) {
            return PageUtil.pageList(selectVo.getPage(), selectVo.getLimit(), 0L, new ArrayList<>());
        }

        QueryWrapper<ZjStoreType> typeQueryWrapper = new QueryWrapper<>();
        typeQueryWrapper.select("id", "name");
        typeQueryWrapper.in("id", outboundExtendList.stream().map(StoreOutboundExtend::getTypeId).distinct().toList())
                .eq("type", 2);
        List<ZjStoreType> storeTypeList = storeTypeService.list(typeQueryWrapper);
        List<StoreOutboundMaterialExtend> materialExtendList = storeOutboundMaterialService.getOutboundMaterialStoreByOutboundIds(outboundExtendList.stream().map(StoreOutboundExtend::getId).toList());

        List<StoreOutboundListVo> voList = outboundExtendList.stream().map(out -> {
            StoreOutboundListVo vo = new StoreOutboundListVo();
            BeanUtil.copyProperties(out, vo);
            vo.setOutboundNo(out.getCode());
            vo.setCreateTime(out.getGmtCreate());
            vo.setUpdateTime(out.getGmtUpdate());
            storeTypeList.stream().filter(f -> Objects.equals(f.getId(), out.getTypeId())).findFirst().ifPresent(type -> vo.setType(type.getName()));
            vo.setStore(materialExtendList.stream().filter(f -> Objects.equals(f.getStoreOutboundId(), out.getId())).map(material -> {
                return material.getStore()+(StringUtils.isNotBlank(material.getStorePositioin())? " " + material.getStorePositioin() : "") + "";
            }).distinct().toList());
            return vo;
        }).toList();
        return PageUtil.pageList(selectVo.getPage(), selectVo.getLimit(), storeOutboundService.getListCount(custAccId, selectVo), voList);
    }

    @Override
    public OutboundDetailVo getOutboundDetail(Long id) {
        ZjStoreOutbound outbound = storeOutboundService.getById(id);
        Assert.notNull(outbound, "出库单参数[ "+id+" ]的数据不存在");
        OutboundDetailVo vo = new OutboundDetailVo();
        BeanUtil.copyProperties(outbound, vo);
        vo.setOutboundNo(outbound.getCode());
        vo.setCreateTime(outbound.getGmtCreate());
        vo.setUpdateTime(outbound.getGmtUpdate());
        vo.setType(storeTypeService.getById(outbound.getTypeId()).getName());
        ZjStoreOutboundReceiptSend receiptSend = storeOutboundReceiptSendService.getById(id);
        vo.setReceiptAddrType(receiptSend.getReceiptAddrType());
        vo.setRelationId(receiptSend.getRelationId());
        vo.setStorePositionId(receiptSend.getStorePositionId());
        vo.setReceiptName(receiptSend.getReceiptName());
        vo.setReceiptPhone(receiptSend.getReceiptPhone());
        vo.setReceiptProvince(receiptSend.getReceiptProvince());
        vo.setReceiptCity(receiptSend.getReceiptCity());
        vo.setReceiptArea(receiptSend.getReceiptArea());
        vo.setReceiptTown(receiptSend.getReceiptTown());
        vo.setReceiptAddress(receiptSend.getReceiptAddress());
        return vo;
    }

    // 根据出库单ID获取出库物料
    @Override
    public List<OutInboundMaterialVo> getOutboundMaterial(Long id) {
        List<StoreOutboundMaterialExtend>  extendList = storeOutboundMaterialService.getOutboundMaterial(id);
        List<Long> goodsIds = extendList.stream().map(StoreOutboundMaterialExtend::getGoodsId).distinct().toList();
        List<Long> goodsSpecIds = extendList.stream().map(StoreOutboundMaterialExtend::getGoodsSpecId).distinct().toList();
        List<ViewGoods> viewGoodsList = viewGoodsService.getByGoodsIdsAndGoodsSpecIds(goodsIds, goodsSpecIds);
        List<ZjStorePosition> positionList = storePositionService.getStorePositionNameByIds(extendList.stream().filter(f -> null != f.getStorePositionId() && f.getStorePositionId() > 0)
                .map(StoreOutboundMaterialExtend::getStorePositionId).distinct().toList());
        List<ZjStoreOutboundMaterialCode> materialCodes = storeOutboundMaterialCodeService.getbyStoreOutboundId(id);
        return extendList.stream().map(extend -> {
            OutInboundMaterialVo vo = new OutInboundMaterialVo();
            BeanUtil.copyProperties(extend, vo);
            viewGoodsList.stream().filter(f -> Objects.equals(f.getGoodsType(), extend.getType()) && Objects.equals(f.getId(), extend.getGoodsId()) && Objects.equals(f.getSpecId(), extend.getGoodsSpecId()))
                    .findFirst().ifPresent(goods -> {
                        vo.setGoodsBrand(goods.getBrand());
                        vo.setGoodsName(goods.getName());
                        vo.setGoodsModel(goods.getModel());
                        vo.setGoodsType(goods.getType());
                        vo.setGoodsUnit(goods.getUnit());
                        vo.setGoodsSpecName(goods.getSpecName());
                    });
            vo.setStorePosition(positionList.stream().filter(f -> Objects.equals(f.getId(), extend.getStorePositionId())).map(ZjStorePosition::getName).findFirst().orElse(""));
            vo.setStockCodeVos(materialCodes.stream().filter(f -> Objects.equals(f.getStoreOutboundMaterialId(), extend.getId())).map(code -> {
                return new StoreGoodsStockCodeVo(code.getCodeId(), code.getCode());
            }).toList());
            return vo;
        }).toList();
    }

    // 确认出库
    @Override
    @Transactional
    public void saveSureOutbound(AccounLoginInfo loginInfo, Long id, StoreOutboundSendVo sendVo) {
        ZjStoreOutbound outbound = storeOutboundService.getById(id);
        Assert.notNull(outbound, "出库单参数[ " + id + " ]的出库数据不存在");
        Assert.isTrue(Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_2),
                (Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_1) ? "出库单未审核，禁止出库" :
                        Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_3) ? "出库单已被驳回，禁止重复操作" :
                                Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_4) ? "出库单已完成，禁止重复操作" : "出库单已作废，禁止重复操作"));
        ZjStoreOutbound updateOutbound = new ZjStoreOutbound();
        updateOutbound.setId(id);
        updateOutbound.setStatus(StoreConstant.STORE_OUT_STATUS_4);
        updateOutbound.setSendGoodsEmpId(loginInfo.getId());
        updateOutbound.setSendGoodsEmp(loginInfo.getName());
        updateOutbound.setSendGoodsTime(new Date());
        updateOutbound.setSendGoodsRemark(sendVo.getRemark());
        updateOutbound.setUpdateEmpId(loginInfo.getId());
        updateOutbound.setUpdateEmp(loginInfo.getName());

        // 查询出库的物料是否有一物一码
        List<ZjStoreOutboundMaterialCode> materialCodeList = storeOutboundMaterialCodeService.getbyStoreOutboundId(id);
        // 是否确认一物一码并出库
        boolean sureCodeAndOutStore = CollectionUtil.isEmpty(materialCodeList);
        if (sureCodeAndOutStore) {
            List<StoreBoundMaterialVo> materialVos = sendVo.getOutBoundMaterialVos();
            Assert.notEmpty(materialVos, "该出库物料没有指定一物一码，请必须绑定一物一码才可出库");
            // 获取存在的物料
            List<ZjStoreOutboundMaterial> outboundMaterialList = storeOutboundMaterialService.getByOutboundId(id);
            ZjStoreOutboundMaterial outboundMaterial = null;

            // 校验物料
            for (StoreBoundMaterialVo vo : materialVos) {
                Assert.notNull(vo.getId(), "确认出库时，出库物料ID参数不能为空");
                vo.setStorePositionId(null == vo.getStorePositionId()? 0 : vo.getStorePositionId());
                outboundMaterial = outboundMaterialList.stream().filter(f -> Objects.equals(f.getId(), vo.getId()) && Objects.equals(f.getType(), vo.getType())
                                && Objects.equals(f.getGoodsId(), vo.getGoodsId()) && Objects.equals(f.getGoodsSpecId(), vo.getGoodsSpecId())).findFirst().orElse(null);
                // 校验物料
                Assert.notNull(outboundMaterial, "出库的物料与原出库的物料不一致");
            }
            List<Long> goodsIds = outboundMaterialList.stream().map(ZjStoreOutboundMaterial::getGoodsId).distinct().toList();
            List<Long> goodsSpecIds = outboundMaterialList.stream().map(ZjStoreOutboundMaterial::getGoodsSpecId).distinct().toList();
            List<ViewGoods> viewGoodsList = viewGoodsService.getByGoodsIdsAndGoodsSpecIds(goodsIds, goodsSpecIds);
            Map<String, ViewGoods> viewGoodsMap = new HashMap<>();

            List<String> codes = new ArrayList<>();
            List<Long> storeIds = new ArrayList<>();
            // 校验仓库
            ViewGoods viewGoods = null;
            for (StoreBoundMaterialVo vo : materialVos) {
                viewGoods = viewGoodsList.stream().filter(f -> Objects.equals(f.getGoodsType(), vo.getType()) && Objects.equals(f.getId(), vo.getGoodsId()) &&
                        Objects.equals(f.getSpecId(), vo.getGoodsSpecId())).findFirst().orElse(null);
                if (null == viewGoods) {
                    continue;
                }
                outboundMaterial = outboundMaterialList.stream().filter(f -> Objects.equals(f.getId(), vo.getId()) && Objects.equals(f.getType(), vo.getType()) && Objects.equals(f.getGoodsId(), vo.getGoodsId())
                        && Objects.equals(f.getGoodsSpecId(), vo.getGoodsSpecId())).findFirst().get();
                // 判断仓库
                Assert.isTrue(Objects.equals(vo.getStoreId(), outboundMaterial.getStoreId()),
                        "物料型号规格[ "+viewGoods.getModel()+ "、"+ viewGoods.getSpecName() +" ]的出库仓库与原出库仓库不一致");
                // 判断仓位
                Assert.isTrue(Objects.equals(vo.getStorePositionId(), outboundMaterial.getStorePositionId()),
                        "物料型号规格[ "+viewGoods.getModel()+ "、"+ viewGoods.getSpecName() +" ]的出库仓位与原出库仓位不一致");
                if (!storeIds.contains(vo.getStoreId())) storeIds.add(vo.getStoreId());
                // 存储一物一码
                if (CollectionUtil.isNotEmpty(vo.getStockCodeVos())) codes.addAll(vo.getStockCodeVos().stream().map(StoreGoodsStockCodeVo::getCode).toList());
                viewGoodsMap.putIfAbsent(vo.getType() + "_" + vo.getGoodsId() + "_" + vo.getGoodsSpecId(), viewGoods);
            }
            // 仓库商品库存
            List<ViewStoreStoreGoodsStock> storeStoreGoodsStockList = viewStoreStoreGoodsStockService.getByStoreIds(storeIds);
            // 校验仓库及物料
            checkStore(materialVos, viewGoodsMap, storeStoreGoodsStockList, codes);

            List<ZjStoreGoodsStockCode> storeGoodsStockCodes = new ArrayList<>();
            List<ZjCode> codeList = new ArrayList<>();
            // 校验一物一码
            checkCode(loginInfo, outbound.getId(), codeList, codes, storeGoodsStockCodes, viewGoodsMap, materialVos);

            materialCodeList.clear();
            ZjStoreOutboundMaterialCode materialCode = null;
            List<ZjStoreGoodsStockCode> storeGoodsStockCodeList = new ArrayList<>();
            ZjStoreGoodsStockCode storeGoodsStockCode = null;
            Long storeGoodsStockCodeId = null;
            for (StoreBoundMaterialVo vo : materialVos) {
                for (StoreGoodsStockCodeVo code : vo.getStockCodeVos()) {
                    materialCode = new ZjStoreOutboundMaterialCode();
                    materialCode.setId(IdWorker.getId());
                    materialCode.setCustAccId(loginInfo.getCustAccId());
                    materialCode.setStoreOutboundId(id);
                    materialCode.setStoreOutboundMaterialId(vo.getId());
                    materialCode.setType(vo.getType());
                    materialCode.setCodeId(code.getCodeId());
                    materialCode.setCode(code.getCode());
                    materialCodeList.add(materialCode);

                    // 仓库商品库存一物一码
                    storeGoodsStockCodeId = storeGoodsStockCodes.stream().filter(f -> Objects.equals(f.getCode(), code.getCode())).findFirst().map(ZjStoreGoodsStockCode::getId).orElse(null);
                    storeGoodsStockCode = new ZjStoreGoodsStockCode();
                    storeGoodsStockCode.setOutStoreType(2);
                    storeGoodsStockCode.setStoreOutboundId(outbound.getId());
                    storeGoodsStockCode.setStoreOutboundCode(outbound.getCode());
                    storeGoodsStockCode.setStoreOutboundMaterialId(outboundMaterial.getId());
                    if (null == storeGoodsStockCodeId) {
                        storeGoodsStockCode.setCustAccId(loginInfo.getCustAccId());
                        storeGoodsStockCode.setStoreId(vo.getStoreId());
                        storeGoodsStockCode.setStorePositionId(vo.getStorePositionId());
                        storeGoodsStockCode.setStoreGoodsStockId(storeStoreGoodsStockList.stream().filter(f -> Objects.equals(f.getId(), vo.getStoreId()) &&
                                Objects.equals(f.getStorePositionId(), vo.getStorePositionId()) && Objects.equals(f.getGoodsType(), vo.getType()) && Objects.equals(f.getGoodsId(), vo.getGoodsId()) &&
                                Objects.equals(f.getGoodsSpecId(), vo.getGoodsSpecId())).findFirst().map(ViewStoreStoreGoodsStock::getStoreGoodsStockId).orElse(0L));
                        storeGoodsStockCode.setCodeId(code.getCodeId());
                        storeGoodsStockCode.setCode(code.getCode());
                        storeGoodsStockCode.setInStoreType(0);
                        storeGoodsStockCode.setStoreInboundId(0L);
                        storeGoodsStockCode.setStoreInboundCode("");
                        storeGoodsStockCode.setStoreInboundMaterialId(0L);
                    } else {
                        storeGoodsStockCode.setId(storeGoodsStockCodeId);
                    }
                    storeGoodsStockCodeList.add(storeGoodsStockCode);
                    // 一物一码更新信息
                    codeList.stream().filter(f -> Objects.equals(f.getId(), code.getCodeId())).forEach(t -> {
                        t.setStoreId(vo.getStoreId());
                        t.setStorePositionId(vo.getStorePositionId());
                        t.setStoreStatus(CodeConstant.STORE_STATUS_5);
                    });
                }
            }
            storeOutboundMaterialCodeService.saveBatch(materialCodeList);
            storeGoodsStockCodeService.saveOrUpdateBatchById(storeGoodsStockCodeList);
            codeService.saveOutStoreActiveCode(loginInfo, "3", storeSetService.getOutStoreCodeActiveMode(loginInfo.getCustAccId()), outbound.getCode(), updateOutbound.getSendGoodsTime(), codeList);
        }
        // 收货人
        ZjStoreOutboundReceiptSend receiptSend = storeOutboundReceiptSendService.getById(id);
        // 工程师、服服务商仓库ID，如果为收货人为工程师、服务商，则需要添加相应的库存
        Long storeId = receiptSend.getReceiptAddrType() == 2 || receiptSend.getReceiptAddrType() == 3? receiptSend.getRelationId() : null;
        if (null != storeId) {
            List<StoreOutServiceGoodsVo> outServiceGoodsVos = new ArrayList<>();
            List<ZjStoreOutboundMaterial> materialList = storeOutboundMaterialService.getByOutboundId(id);
            for (ZjStoreOutboundMaterial vo : materialList) {
                outServiceGoodsVos.add(new StoreOutServiceGoodsVo(storeId, vo.getType(), vo.getGoodsId(), vo.getGoodsSpecId(), vo.getQty()));
            }
            storeOutServiceGoods(loginInfo.getCustAccId(), false, outServiceGoodsVos);
        }
        ZjStoreOutboundReceiptSend send = new ZjStoreOutboundReceiptSend();
        BeanUtil.copyProperties(sendVo, send);
        send.setId(id);

        List<ZjStoreOutboundMaterialCode> materialCodes = storeOutboundMaterialCodeService.getbyStoreOutboundId(id);
        List<Long> codeIds = materialCodes.stream().map(ZjStoreOutboundMaterialCode::getCodeId).toList();
        // 避免重复更新编码
        if (!sureCodeAndOutStore) {
            codeService.updateCodeByOutInbound(loginInfo, codeIds, updateOutbound.getSendGoodsTime(), true);
        }
        storeOutboundService.updateById(updateOutbound);
        storeOutboundReceiptSendService.updateById(send);
        storeLogisticsService.saveStoreOutboundLogistic(loginInfo.getCustAccId(), id, new StoreLogisticsVo()
                .setStoreOutboundCode(outbound.getCode())
                .setLogisticsName(sendVo.getLogisticsName())
                .setLogisticsNo(sendVo.getLogisticsNo())
                .setLogisticsCode(sendVo.getLogisticsCode())
                .setTime(updateOutbound.getSendGoodsTime())
                .setRemark(sendVo.getRemark())
        );
       StoreBoundServiceLog.saveOutboundLog(new StoreBoundVo(loginInfo, outbound.getId(), outbound.getCode(),  "确认出库", sendVo.getRemark()));
    }

    // 作废出库单
    @Override
    @Transactional
    public void cancelOutbound(AccounLoginInfo loginInfo, Long id, String remark) {
        ZjStoreOutbound outbound = storeOutboundService.getById(id);
        Assert.notNull(outbound, "出库单参数[ " + id + " ]的出库数据不存在");
        Assert.isTrue(Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_1) || Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_2),
                (Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_3) ? "出库单已被驳回，禁止作废操作" :
                        Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_4) ? "出库单已完成，禁止作废操作" : "出库单已作废，禁止重复操作"));

        ZjStoreOutbound updateOutbound = new ZjStoreOutbound();
        updateOutbound.setId(id);
        updateOutbound.setStatus(StoreConstant.STORE_OUT_STATUS_5);
        updateOutbound.setCancelEmpId(loginInfo.getId());
        updateOutbound.setCancelEmp(loginInfo.getName());
        updateOutbound.setCancelTime(new Date());
        updateOutbound.setCancelRemark(remark);
        updateOutbound.setBackInventory(true);
        updateOutbound.setBackInventoryTime(new Date());
        updateOutbound.setUpdateEmpId(loginInfo.getId());
        updateOutbound.setUpdateEmp(loginInfo.getName());

        exceuteUpdate(loginInfo, outbound, true);
        storeOutboundService.updateById(updateOutbound);
        StoreBoundServiceLog.saveOutboundLog(new StoreBoundVo(loginInfo, outbound.getId(), outbound.getCode(),  "作废出库单", remark));
    }

    // 驳回出库单
    @Override
    @Transactional
    public void rejectOutbound(AccounLoginInfo loginInfo, Long id, String remark) {
        ZjStoreOutbound outbound = storeOutboundService.getById(id);
        Assert.notNull(outbound, "出库单参数[ " + id + " ]的出库数据不存在");
        Assert.isTrue(Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_1),
                Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_2)? "出库单已审核，不可再驳回操作" :
                (Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_3) ? "出库单已被驳回，禁止重复操作" :
                        Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_4) ? "出库单已完成，禁止作废操作" : "出库单已作废，禁止驳回操作"));
        ZjStoreOutbound updateOutbound = new ZjStoreOutbound();
        updateOutbound.setId(id);
        updateOutbound.setStatus(StoreConstant.STORE_OUT_STATUS_3);
        updateOutbound.setRejectEmpId(loginInfo.getId());
        updateOutbound.setRejectEmp(loginInfo.getName());
        updateOutbound.setRejectTime(new Date());
        updateOutbound.setRejectRemark(remark);
        updateOutbound.setBackInventory(true);
        updateOutbound.setBackInventoryTime(new Date());
        updateOutbound.setUpdateEmpId(loginInfo.getId());
        updateOutbound.setUpdateEmp(loginInfo.getName());

        exceuteUpdate(loginInfo, outbound, false);
        storeOutboundService.updateById(updateOutbound);
        StoreBoundServiceLog.saveOutboundLog(new StoreBoundVo(loginInfo, outbound.getId(), outbound.getCode(),  "审核驳回出库单", remark));
    }

    /**
     * 取消或驳回出库单执行更新的操作
     * @param loginInfo 登录信息
     * @param outbound 出库单对象
     * @param cancelOutbound 是否取消出库单：true-是，false-不是，为驳回操作
     */
    private void exceuteUpdate(AccounLoginInfo loginInfo, ZjStoreOutbound outbound, boolean cancelOutbound) {
        // 更新商品库存
        List<ZjStoreOutboundMaterial> outboundMaterials = storeOutboundMaterialService.getByOutboundId(outbound.getId());
        List<ZjStoreGoodsStockCode> goodsStockCodeList = storeGoodsStockCodeService.getByStoreOutboundId(outbound.getId());
        if (CollectionUtil.isEmpty(goodsStockCodeList)) {
            storeOutboundMaterialService.updateStatusByOutbountId(outbound.getId(), 3);
            return;
        }
        List<ZjStoreGoodsStock> storeGoodsStocks = storeGoodsStockService.listByIds(goodsStockCodeList.stream().map(ZjStoreGoodsStockCode::getStoreGoodsStockId).distinct().toList());
        List<ZjStoreGoodsStock> updateStoreGoodsStockList = new ArrayList<>();
        ZjStoreGoodsStock updateStoreGoodsStock = null;
        // 出库单是否已审核
        boolean checkOutbound = Objects.equals(outbound.getStatus(), StoreConstant.STORE_OUT_STATUS_2);
        long outboundGoodsQty = 0;
        for (ZjStoreGoodsStock storeGoodsStock : storeGoodsStocks) {
            updateStoreGoodsStock = new ZjStoreGoodsStock();
            updateStoreGoodsStock.setId(storeGoodsStock.getId());
            outboundGoodsQty = outboundMaterials.stream().filter(f -> goodsStockCodeList.stream().anyMatch(f2 -> Objects.equals(f2.getStoreOutboundMaterialId(), f.getId()))).map(ZjStoreOutboundMaterial::getQty).findFirst().orElse(0L);
            if (checkOutbound) {
                updateStoreGoodsStock.setSystemStock(new BigDecimal(storeGoodsStock.getSystemStock().toString()).add(new BigDecimal(outboundGoodsQty + "")).longValue());
            }
            updateStoreGoodsStockList.add(updateStoreGoodsStock);
        }
        List<ZjCode> codeList = codeService.checkByCodes(loginInfo.getCustAccId(), goodsStockCodeList.stream().map(ZjStoreGoodsStockCode::getCode).toList());

        List<ZjStoreGoodsStockCode> updateStoreGoodsStockCodeList = new ArrayList<>();
        List<Long> delStoreGoodsStockCodeIds = new ArrayList<>();
        ZjStoreGoodsStockCode updateGoodsStockCode = null;
        List<ZjCode> updateCodeList = new ArrayList<>();
        ZjCode updateCode = null, code = null;
        for (ZjStoreGoodsStockCode stockCode : goodsStockCodeList) {
            code = codeList.stream().filter(f -> Objects.equals(f.getSecurityCode(), stockCode.getCode())).findFirst().get();
            updateCode = new ZjCode();

            if (null != stockCode.getStoreInboundId() && stockCode.getStoreInboundId() > 0) {
                updateGoodsStockCode = new ZjStoreGoodsStockCode();
                BeanUtil.copyProperties(stockCode, updateGoodsStockCode);
                updateGoodsStockCode.setOutStoreType(0);
                updateGoodsStockCode.setStoreOutboundId(0L);
                updateGoodsStockCode.setStoreOutboundCode("");
                updateGoodsStockCode.setStoreOutboundMaterialId(0L);
                updateStoreGoodsStockCodeList.add(updateGoodsStockCode);

                updateCode.setStoreStatus(CodeConstant.STORE_STATUS_3);
                updateCode.setStoreId(code.getStoreId());
                updateCode.setStorePositionId(code.getStorePositionId());
            } else {
                delStoreGoodsStockCodeIds.add(stockCode.getId());

                updateCode.setStoreStatus(CodeConstant.STORE_STATUS_1);
                updateCode.setStoreId(0L);
                updateCode.setStorePositionId(0L);
            }
            updateCode.setId(stockCode.getCodeId());
            updateCode.setOutStoreActiva(false);
            updateCode.setOutStoreWarranty(false);
            if (code.getOutStoreActiva()) {
                updateCode.setActiva(false);
                updateCode.setActivaTime(null);
            } else {
                updateCode.setActiva(code.getActiva());
                updateCode.setActivaTime(code.getActivaTime());
            }
            if (code.getOutStoreWarranty()) {
                updateCode.setWarrantyStartTime(null);
                updateCode.setWarrantyEndTime(null);
            } else {
                updateCode.setWarrantyStartTime(code.getWarrantyStartTime());
                updateCode.setWarrantyEndTime(code.getWarrantyEndTime());
            }
            updateCodeList.add(updateCode);
        }
        storeOutboundMaterialService.updateStatusByOutbountId(outbound.getId(), 3);
        storeGoodsStockService.updateBatchById(updateStoreGoodsStockList);
        if (CollectionUtil.isNotEmpty(updateStoreGoodsStockCodeList)) storeGoodsStockCodeService.saveOrUpdateBatchById(updateStoreGoodsStockCodeList);
        if (CollectionUtil.isNotEmpty(delStoreGoodsStockCodeIds)) storeGoodsStockCodeService.deleteByIds(delStoreGoodsStockCodeIds);
        codeService.updateOutStoreActiveCode(loginInfo, true, cancelOutbound, updateCodeList);
    }


    // 工程师、服务商物料出入库操作
    @Override
    public void storeOutServiceGoods(Long custAccId, boolean outStore, List<StoreOutServiceGoodsVo> storeOutServiceGoodsVos) {
        if (CollectionUtil.isEmpty(storeOutServiceGoodsVos)) {
            return;
        }
        // 处理相同项的合并
        List<StoreOutServiceGoodsVo> outServiceGoodsVos = new ArrayList<>();
        for (StoreOutServiceGoodsVo vo : storeOutServiceGoodsVos) {
            if (CollectionUtil.isNotEmpty(outServiceGoodsVos) && outServiceGoodsVos.stream().anyMatch(f -> Objects.equals(f.getStoreId(), vo.getStoreId()) && Objects.equals(f.getGoodsType(), vo.getGoodsType())
                    && Objects.equals(f.getGoodsId(), vo.getGoodsId()) && Objects.equals(f.getGoodsSpecId(), vo.getGoodsSpecId()))) {
                outServiceGoodsVos.stream().filter(f -> Objects.equals(f.getStoreId(), vo.getStoreId()) && Objects.equals(f.getGoodsType(), vo.getGoodsType())
                        && Objects.equals(f.getGoodsId(), vo.getGoodsId()) && Objects.equals(f.getGoodsSpecId(), vo.getGoodsSpecId())).forEach(t -> {
                            t.setQty(t.getQty() + vo.getQty());
                });
                continue;
            }
            outServiceGoodsVos.add(vo);
        }
        List<Long> storeIds = outServiceGoodsVos.stream().map(StoreOutServiceGoodsVo::getStoreId).toList();
        List<Long> goodsIds = outServiceGoodsVos.stream().map(StoreOutServiceGoodsVo::getGoodsId).toList();
        List<Long> goodsSpecIds = outServiceGoodsVos.stream().map(StoreOutServiceGoodsVo::getGoodsSpecId).toList();
        List<ZjStoreGoodsStock> goodsStockList = storeGoodsStockService.getByStoreIdsAndGoodsIdsAndGoodsSpecIds(storeIds, goodsIds, goodsSpecIds);

        List<ZjStoreGoodsStock> addGoodsStock = new ArrayList<>();
        List<ZjStoreGoodsStock> updateGoodsStock = new ArrayList<>();
        ZjStoreGoodsStock stock = null;
        for (StoreOutServiceGoodsVo vo : outServiceGoodsVos) {
            stock = goodsStockList.stream().filter(f -> Objects.equals(f.getStoreId(), vo.getStoreId()) && Objects.equals(f.getGoodsType(), vo.getGoodsType())
                    && Objects.equals(f.getGoodsId(), vo.getGoodsId()) && Objects.equals(f.getGoodsSpecId(), vo.getGoodsSpecId())).findFirst().orElse(null);
            if (null != stock) {
                stock.setSystemStock(outStore ? new BigDecimal(stock.getSystemStock().toString()).subtract(new BigDecimal(vo.getQty().toString())).longValue() :
                        new BigDecimal(stock.getSystemStock().toString()).add(new BigDecimal(vo.getQty().toString())).longValue());
                updateGoodsStock.add(stock);
                continue;
            }
            stock = new ZjStoreGoodsStock();
            BeanUtil.copyProperties(vo, stock);
            stock.setId(IdWorker.getId());
            stock.setCustAccId(custAccId);
            stock.setStoreId(vo.getStoreId());
            stock.setStorePositionId(0L);
            stock.setSystemStock(outStore ? new BigDecimal("0").subtract(new BigDecimal(vo.getQty().toString())).longValue() : vo.getQty());
            stock.setPrice(new BigDecimal("0.0"));
            addGoodsStock.add(stock);
        }
        if (CollectionUtil.isNotEmpty(addGoodsStock)) storeGoodsStockService.saveBatch(addGoodsStock);
        if (CollectionUtil.isNotEmpty(updateGoodsStock)) storeGoodsStockService.updateBatchById(updateGoodsStock);
    }

}
